<?php
// +----------------------------------------------------------------------
// | B5Yii2CMF V3.0 [快捷通用基础管理开发平台]
// +----------------------------------------------------------------------
// | Author: 冰舞 <357145480@qq.com>
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace api\utils\libs;


use common\models\system\Admin;
use common\models\system\AdminDept;
use common\services\PermissionService;
use common\services\system\DeptService;
use Yii;
use yii\db\Query;

class DataScope
{
    const NONE_SCOPE = 0; // 无数据权限
    const ALL_SCOPE = 1; // 全部数据权限
    const DEPT_LEVEL_SCOPE = 2; // 本部门及以下数据权限
    const DEPT_SELF_SCOPE = 4; // 本部门数据权限
    const CUSTOM_SCOPE = 8; // 自定数据权限
    const USER_SCOPE = 16; // 仅本人数据权限

    protected ?Admin $user = null;
    protected bool $onlyUser = false; // 是否只根据用户ID判断
    protected int $cacheTime = -1; // 缓存时间 <=0 不使用缓存
    protected string $cacheName = 'dataScope_user_';

    public function __construct(Admin $user, $cacheTime = -1)
    {
        $this->cacheTime = $cacheTime;
        $this->user = $user;
    }

    // 静态实例化
    public static function instance(Admin $user, $cacheTime = -1): DataScope
    {
        return new DataScope($user, $cacheTime);
    }

    // 设置只根据用户ID判断
    public function setOnlyUser(bool $onlyUser): DataScope
    {
        $this->onlyUser = $onlyUser;
        return $this;
    }

    /**
     * 拼接数据权限条件
     * 表中必须含有用户的组织id或用户的id， 默认组织字段为dept_id，用户id位user_id
     *
     * $model = xxx::find()->where(['xxx'=>xxx]);
     * $model = (new Query())->from('xxx')
     * $list = DataScope::instance($user)->query($model)->all();
     *
     * @param Query $model
     * @param string $deptField 组织架构字段名
     * @param string $userField 用户字段名
     * @param string $deptAlias 关联查询时 表的别名
     * @param string $userAlias 关联查询时的表别名
     * @return Query
     */
    public function query(Query $model, string $deptField = 'dept_id', string $userField = 'user_id', string $deptAlias = '', string $userAlias = ''): Query
    {
        if ($this->onlyUser) {
            if (!$userField) return $model->andWhere('1=0');
            $filter = $this->getAuthDeptList();
            if ($filter === true) return $model;
            $userIds = $filter ? ($filter['user'] ?? []) : [];
            if (!$userIds) return $model->andWhere('1=0');
            $userField = ($userAlias ? $userAlias . '.' : '') . $userField;
            $model = $model->andWhere([$userField => $userIds]);
        } else {
            if (!$deptField && !$userField) return $model->andWhere('1=0');
            $filter = $this->getAuthDeptList();
            if ($filter === true) return $model;

            $deptList = $filter ? ($filter['dept'] ?? []) : [];
            $userIds = $filter ? ($filter['user'] ?? []) : [];
            if (!$deptList && !$userIds) return $model->andWhere('1=0');

            $deptField = $deptField ? (($deptAlias ? $deptAlias . '.' : '') . $deptField) : '';
            $userField = $userField ? (($userAlias ? $userAlias . '.' : '') . $userField) : '';
            if ($deptField && $userField) {
                if ($deptList && $userIds) {
                    $model = $model->andWhere(['or', [$userField => $userIds], [$deptField => $deptList]]);
                } elseif ($deptList) {
                    $model = $model->andWhere([$deptField => $deptList]);
                } elseif ($userIds) {
                    $model = $model->andWhere([$userField => $userIds]);
                }
            } elseif ($deptField) {
                $model = $model->andWhere([$deptField => $deptList]);
            } elseif ($userIds) {
                $model = $model->andWhere([$userField => $userIds]);
            }
        }
        return $model;
    }

    /**
     * 列表数组进行数据过滤
     * $list = [
     *      ['dept_id'=>xx,'user_id'=>xx, ...],
     *      ['dept_id'=>xx,'user_id'=>xx, ...],
     * ]
     * $list = DataScope::instance($user)->array($list);
     *
     * @param array $list
     * @param string $deptField 组织字段
     * @param string $userField 用户字段 可以为空 只检查组织
     * @return array
     */
    public function array(array $list = [], string $deptField = 'dept_id', string $userField = 'user_id'): array
    {
        if (empty($list)) return [];
        $filter = $this->getAuthDeptList();
        if ($filter === true) return $list;

        $deptList = $filter ? ($filter['dept'] ?? []) : [];
        $userIds = $filter ? ($filter['user'] ?? []) : [];
        if (!$deptList && !$userIds) return [];

        foreach ($list as $key => $value) {
            $dept_id = $deptField ? ($value[$deptField] ?? 0) : 0;
            $user_id = $userField ? ($value[$userField] ?? 0) : 0;

            if ($deptField && $userField) {
                if ($deptList && $userIds) {
                    if (!in_array($dept_id, $deptList) && !in_array($user_id, $userIds)) {
                        unset($list[$key]);
                    }
                } elseif ($deptList) {
                    if (!in_array($dept_id, $deptList)) {
                        unset($list[$key]);
                    }
                } elseif ($userIds) {
                    if (!in_array($user_id, $userIds)) {
                        unset($list[$key]);
                    }
                }
            } elseif ($deptField) {
                if (!in_array($dept_id, $deptList)) {
                    unset($list[$key]);
                }
            } elseif ($userField) {
                if (!in_array($user_id, $userIds)) {
                    unset($list[$key]);
                }
            }

        }
        return $list;
    }

    /**
     * 对组织架构进行数据权限过滤
     * 主要是将过滤后的根部门的parent_id改为0，为了树形显示
     * $deptList = [
     *      ['id'=>xx, 'name' => 'xxx', 'parent_id'=>xx, ...],
     *      ['id'=>xx, 'name' => 'xxx', 'parent_id'=>xx, ...],
     * ]
     * $deptList = DataScope::instance($user)->dept($deptList);
     *
     * @param array $deptList
     * @param bool $rootParent 是否将一级的parent_id置为0
     * @param string $id_field
     * @param string $parent_field
     * @return array
     */
    public function dept(array $deptList, bool $rootParent = true, string $id_field = 'id', string $parent_field = 'parent_id'): array
    {
        if (empty($deptList)) return [];
        $filter = $this->getAuthDeptList();
        if ($filter === true) return $deptList;

        $deptAuthList = $filter ? ($filter['dept'] ?? []) : [];
        if (!$deptAuthList) return [];

        $idList = [];
        foreach ($deptList as $key => $value) {
            if (in_array($value[$id_field], $deptAuthList)) {
                $idList[] = $value[$id_field];
            } else {
                unset($deptList[$key]);
            }
        }
        if (!$idList) return [];

        if($rootParent){
            foreach ($deptList as $key => $value) {
                if (!in_array($value[$parent_field], $idList)) {
                    $value[$parent_field] = 0;
                    $deptList[$key] = $value;
                }
            }
        }
        return array_values($deptList);
    }

    /**
     * 检查某人是否有某个部门或人的操作数据权限
     * @param $dept_id
     * @param $user_id
     * @return bool
     */
    public function check($dept_id, $user_id): bool
    {
        $filter = $this->getAuthDeptList();
        if ($filter === true) return true;

        $deptList = $filter ? ($filter['dept'] ?? []) : [];
        $userIds = $filter ? ($filter['user'] ?? []) : [];

        if ($deptList && in_array($dept_id, $deptList)) return true;
        if ($userIds && in_array($user_id, $userIds)) return true;

        return false;
    }

    // 获取用户数据权限详情
    public function getAuthDeptList(){
        if($this->cacheTime > 0){
            $cache = Yii::$app->cache->get($this->cacheName.$this->user->id);
            if($cache) return $cache;
        }
        $data = $this->getAuthDeptListQuery();
        Yii::$app->cache->set($this->cacheName.$this->user->id,$data,$this->cacheTime);
        return $data;
    }

    /**
     * 获取数据权限过滤用户的组织id数组
     * 超管返回ture 所有数据；
     * 没有权限组织或角色返回false，没有任何数据
     * dept 权限组织id列表
     * user 返回false不判断 否则判断
     * @return array|bool
     */
    private function getAuthDeptListQuery()
    {
        if (!$this->user) return false;

        $isAdmin = PermissionService::isAdmin($this->user->id);
        if ($isAdmin) return true;  // 超管返回全部权限

        $roleList = $this->user->roleList;
        if (!$roleList) return false; // 无角色返回无权限

        $dataScope = array_sum(array_column($roleList, 'data_scope')); // 用户角色列表
        if ($dataScope <= self::NONE_SCOPE) return false;

        if (!(31 & $dataScope)) return false; // 无效的键值

        // 全部数据权限
        if (self::ALL_SCOPE & $dataScope) return true;

        $deptIds = $this->user->deptLink ? array_column($this->user->deptLink, 'dept_id') : [];
        if (!$deptIds) return false; // 无部门组织 返回无权限

        $deptArr = []; // 组织id列表
        $isUser = false; // 是否含有本人数据


        // 本部门及以下数据权限
        if (self::DEPT_LEVEL_SCOPE & $dataScope) {
            foreach ($deptIds as $deptId) {
                $deptArr[] = $deptId;
                $childList = DeptService::getChildList($deptId, true);
                $deptArr = $childList ? array_merge($deptArr, $childList) : $deptArr;
            }
        }
        // 本部门数据权限
        if (self::DEPT_SELF_SCOPE & $dataScope) {
            $deptArr = array_merge($deptArr, $deptIds);
        }

        // 自定义部门权限
        if (self::CUSTOM_SCOPE & $dataScope) {
            foreach ($roleList as $role) {
                if ($role->data_scope != self::CUSTOM_SCOPE) continue;
                $deptList = $role->deptIds ? array_column($role->deptIds, 'dept_id') : [];
                $deptArr = $deptList ? array_merge($deptArr, $deptList) : $deptArr;
            }
        }

        //个人数据 优先级最低
        if (self::USER_SCOPE & $dataScope) {
            $isUser = true;
        }
        $deptArr = array_unique($deptArr);

        // 如果只根据用户判断，则返回用户id
        $userIds = $isUser ? [$this->user->id] : [];
        if ($this->onlyUser && $deptArr) {
            $deptUsers = AdminDept::find()->distinct()->select(['user_id'])->where(['dept_id' => $deptArr])->column();
            if ($deptUsers) $userIds = array_merge($userIds, $deptUsers);
        }

        return ['dept' => $deptArr, 'user' => $userIds];
    }


}